package graphicsExtended.animation;

import graphicsExtended.animation.panelDrawer.AnimationPanelDrawer;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
import system.SystemUtils;
import time.Timer;

/**
 *
 * Fully Tested: No
 * Fully Documented: No
 * Created: 29-May-2012
 * Last Updated: 29-May-2012
 *
 * @author Shimu Wu
 */
public abstract class AnimationPanel extends JPanel {

    private int delayTime;

    private int fps;

    private final AnimationPanelDrawer DRAWER;
    
    public AnimationPanel(int width, int height) {
        this(width, height, 10, new AnimationPanelDrawer(width, height));
    }

    /**
     * Instantiates an AnimationPanel with the given animator, width,
     * height, fps, cps, and coordinateSpace.
     * 
     * @param animator
     * @param width the width of the container panel
     * @param height the height of the container panel
     * @param fps the frames per second of the container, i.e. the number
     * of times the given animator's drawFrame method is called in a second.
     * @param cps the number of times the given animator's calculate method
     * is called in a second. 
     * @param coordinateSpace one of AnimationPanel.TOP_LEFT, or BOTTOM_LEFT.
     * If the given coordinateSpace is not one of the above, the default
     * coordinateSpace is used (TOP_LEFT).
     */
    public AnimationPanel(int width, int height, int fps, 
            AnimationPanelDrawer drawer) {
        super.setSize(width, height);
        super.setPreferredSize(new Dimension(width, height));
        this.DRAWER = drawer;
        setFramesPerSecond(fps);
    }

    public AnimationPanelDrawer getDrawer() {
        return this.DRAWER;
    }
    
    /**
     * Calculate the delayTime required for the given fps (frames per second).
     * Returns 0 if the fps is 0 or less.
     * 
     * @param fps
     * @return 
     */
    private int getDelayTime(int fps) {
        // Calculate the delayTime
        if (fps > 1) {
            return (int) (1000 / (double) fps);
        }
        return 0;
    }

    /**
     * Sets the number of frames per second of the container.
     * 
     * @param fps 
     */
    public final void setFramesPerSecond(int fps) {
        this.fps = fps;
        this.delayTime = getDelayTime(fps);
    }

    // ============= OVERRIDED METHODS FROM JPANEL =============
    /**
     * Returns the transformation applied to this graphics
     * 
     * @return 
    
    public final AffineTransform getTransform() {
    return this.transform;
    }*/
    @Override
    public final void paint(Graphics g) {
        DRAWER.paint(g);
    }

    @Override
    public void update(Graphics g) {
        this.paint(g);
    }

    public int getFramesPerSecond() {
        return this.fps;
    }

    /**
     * Starts a new thread that constantly updates this AnimationPanel's 
     * graphics. 
     */
    public void run() {
        System.out.println("Loading ...");
        this.init();
        System.out.println("Done Loading ...");

        // Run the frame updating       
        Runnable frameUpdaterRunnable = new Runnable() {
            @Override
            public void run() {
                Timer timer = new Timer();
                while (true) {
                    timer.start();
                    repaint();
                    long timeLeft = delayTime - timer.getElapsedTime();
                    if (timeLeft > 0) {
                        SystemUtils.sleep((int) timeLeft);
                    }
                }
            }
        };
        
        Thread frameUpdater = new Thread(frameUpdaterRunnable);
        frameUpdater.start();

        // Start the animate() method in a separate thread
        Runnable animatorRunnable = new Runnable() {

            @Override
            public void run() {
                animate();
            }
        };

        // Delay some time for the first graphics to draw 
        // before starting the animate thread
        SystemUtils.sleep(delayTime);

        Thread animatorThread = new Thread(animatorRunnable);
        animatorThread.start();
    }

    // ================== ABSTRACT METHODS ==================
    /**
     * Called by the AnimationFrame2 once on start up. Any initializing and
     * calculations prior to the first image shown to the screen should
     * be in this method. Must halt (no infinite loop), place any infinitely
     * looping methods in run().
     * 
     */
    public abstract void init();

    /**
     * Called once, after calling init(JPanel). 
     * Allowed to infinitely loop (in the case of an animation).
     */
    public abstract void animate();
}